package me.xiaoyan.android.widget;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 
 * @author xiaoyan
 * 字符串加密以及解密函数 (英文)
 * AuthCode uc = new AuthCode();
 * uc.authcode(str, "ENCODE",key); //加密
 * uc.authcode(str, "DECODE",key); //解密密
 */
public class AuthCode {

	private static AuthCode ac = new AuthCode();
	/**
	 * 字符串加密以及解密函数
	 * @param str 原文或者密文
	 * @param operation  operation 操作(ENCODE | DECODE), 默认为 DECODE
	 * @return
	 */
	public String authcode(String str, String operation) {
		return authcode(str, operation, null);
	}
	
	private AuthCode(){}
	
	public static AuthCode getInstance(){
		return ac;
	}

	/**
	 * 字符串加密以及解密函数
	 * @param str 原文或者密文
	 * @param operation  operation 操作(ENCODE | DECODE), 默认为 DECODE
	 * @param key 密钥
	 * @return
	 */
	public String authcode(String str, String operation, String key) {
		return authcode(str, operation, key, 0);
	}

	/**
	 * 字符串加密以及解密函数
	 * @param str 原文或者密文
	 * @param operation  operation 操作(ENCODE | DECODE), 默认为 DECODE
	 * @param key 密钥
	 * @param expiry 有效期
	 * @return
	 */
	public String authcode(String str, String operation, String key,
			int expiry) {

		int ckey_length = 4; // 随机密钥长度 取值 0-32;
		// 加入随机密钥，可以令密文无任何规律，即便是原文和密钥完全相同，加密结果也会每次不同，增大破解难度。
		// 取值越大，密文变动规律越大，密文变化 = 16 的 ckey_length 次方
		// 当此值为 0 时，则不产生随机密钥

		key = md5(key);
		String keya = md5(substr(key, 0, 16));
		String keyb = md5(substr(key, 16, 16));
		String keyc = ckey_length > 0 ? (operation.equals("DECODE") ? substr(
				str, 0, ckey_length) : substr(md5(microtime()),
				-ckey_length))
				: "";

		String cryptkey = keya + md5(keya + keyc);
		int key_length = cryptkey.length();

		str = operation.equals("DECODE") ? base64_decode(substr(str,
				ckey_length)) : sprintf("%010d", expiry > 0 ? expiry
				+ time() : 0)
				+ substr(md5(str + keyb), 0, 16) + str;
		int str_length = str.length();

		StringBuffer result1 = new StringBuffer();

		int[] box = new int[256];
		for (int i = 0; i < 256; i++) {
			box[i] = i;
		}

		int[] rndkey = new int[256];
		for (int i = 0; i <= 255; i++) {
			rndkey[i] = cryptkey.charAt(i % key_length);
		}

		int j = 0;
		for (int i = 0; i < 256; i++) {
			j = (j + box[i] + rndkey[i]) % 256;
			int tmp = box[i];
			box[i] = box[j];
			box[j] = tmp;
		}

		j = 0;
		int a = 0;
		for (int i = 0; i < str_length; i++) {
			a = (a + 1) % 256;
			j = (j + box[a]) % 256;
			int tmp = box[a];
			box[a] = box[j];
			box[j] = tmp;

			result1.append((char) ((str.charAt(i)) ^ (box[(box[a] + box[j]) % 256])));

		}

		if (operation.equals("DECODE")) {
			String result = result1.substring(0, result1.length());
			if ((Integer.parseInt(substr(result.toString(), 0, 10)) == 0 || Long
					.parseLong(substr(result.toString(), 0, 10)) - time() > 0)
					&& substr(result.toString(), 10, 16).equals(
							substr(md5(substr(result.toString(), 26) + keyb),
									0, 16))) {
				return substr(result.toString(), 26);
			} else {
				return "";
			}
		} else {
			return keyc
					+ base64_encode(result1.toString()).replaceAll("=", "");
		}
	}

	public String md5(String input){
		input = null == input ? "" : input;
		MessageDigest md;
		try {
			md = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			return null;
		}	
		return byte2hex(md.digest(input.getBytes()));
	}

	public String md5(long input){
		return md5(String.valueOf(input));
	}
	

	public String base64_decode(String input){
		String code = null;
		try {
			code = new String(Base64.decode(input.toCharArray()),"iso-8859-1");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return e.getMessage();
		}
		return code;
	}
	
	public String base64_encode(String input){
		String code = null;
		try {
			code = new String(Base64.encode(input.getBytes("iso-8859-1")));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return code;
	}

	private String byte2hex(byte[] b) {
		StringBuffer hs = new StringBuffer();
		String stmp = "";
		for (int n = 0; n < b.length; n++) {
			stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));
			if (stmp.length() == 1)
				hs.append("0").append(stmp);
			else
				hs.append(stmp);
		}
		return hs.toString();
	}

	private String substr(String input,int begin, int length){
		return input.substring(begin, begin+length);
	}
	private String substr(String input,int begin){
		if(begin>0){
			return input.substring(begin);
		}else{
			return input.substring(input.length()+ begin);
		}
	}
	private long microtime(){
		return System.currentTimeMillis();
	}
	private long time(){
		return System.currentTimeMillis()/1000;
	}	
	private String sprintf(String format, long input){
		String temp = "0000000000"+input;
		return temp.substring(temp.length()-10);
	}
}
